package org.cuit.lmq.core;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;


/**
 * 到时候可以有一个建议库。
 * 最终决定总共有八种状态，
 * low-normal-high-devil 子状态
 * 休息 lowTrain-moreSleep-normalEat 3
 * 调养 normalTrain-normalSleep-normalEat 4-1
 * 增肌 highTrain-moreSleep-highEat 2
 * 减脂 highTrain-normalSleep-lowEat 3
 * 补水 normalTrain-moreWater-normalEat 2
 * 加练 devilTrain-devilEat-moreSleep 3
 * 耐力 normalTrain-normalSleep-normalEat 4-1
 * 爆发 devilTrain-normalSleep-devilEat
 * <p>
 * 在程序初始化的时候就开始构建决策树，@CommondLine，（这里可以用比较好的数据来构建好，然后放到内存里面，供求之后使用）
 * 每次需要进行计划推荐的时候就直接提取数据库中的最新的数据，开始进行推荐。
 *
 * @author Seon
 * @date 2023/3/10
 */
@Slf4j
@Data
@NoArgsConstructor
public class DecisionTree {

    public ArrayList<String> label = new ArrayList<String>();//特征标签
    public ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();//数据集
    public ArrayList<ArrayList<String>> test = new ArrayList<ArrayList<String>>();//测试数据集
    public ArrayList<String> sum = new ArrayList<String>();//分类种类数
    public String kind;

    public DecisionTree(String path) throws FileNotFoundException {
        //初始化训练数据并得到分类种数
        getData(path);
        init(data);
    }

    public void init(ArrayList<ArrayList<String>> data) {
        //得到种类数
        sum.add(data.get(0).get(data.get(0).size() - 1));
        for (int i = 0; i < data.size(); i++) {
            if (sum.contains(data.get(i).get(data.get(0).size() - 1)) == false) {
                sum.add(data.get(i).get(data.get(0).size() - 1));
            }
        }
    }

    //获取测试数据集
    public void getTestData(String path) throws FileNotFoundException {
        String str;
        int i = 0;
        try {
            //BufferedReader in=new BufferedReader(new FileReader(path));
            FileInputStream fis = new FileInputStream(path);
            InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
            BufferedReader in = new BufferedReader(isr);
            while ((str = in.readLine()) != null) {
                String[] strs = str.split(",");
                ArrayList<String> line = new ArrayList<String>();
                for (int j = 0; j < strs.length; j++) {
                    line.add(strs[j]);
                    //System.out.print(strs[j]+" ");
                }
                test.add(line);
                //System.out.println();
                i++;
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取训练数据集
    public void getData(String path) throws FileNotFoundException {
        String str;
        int i = 0;
        try {
            InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
//            FileInputStream fis = new FileInputStream(path);
            InputStreamReader isr = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8);
            BufferedReader in = new BufferedReader(isr);
            while ((str = in.readLine()) != null) {
                if (i == 0) {
                    String[] strs = str.split(",");
                    for (int j = 0; j < strs.length; j++) {
                        label.add(strs[j]);
                        //System.out.print(strs[j]+" ");
                    }
                    i++;
                    //System.out.println();
                    continue;
                }
                String[] strs = str.split(",");
                ArrayList<String> line = new ArrayList<String>();
                for (int j = 0; j < strs.length; j++) {
                    line.add(strs[j]);
                    //System.out.print(strs[j]+" ");
                }
                data.add(line);
                //System.out.println();
                i++;
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public double Ent(ArrayList<ArrayList<String>> dat) {
        //计算总的信息熵
        int all = 0;
        double amount = 0.0;
        for (int i = 0; i < sum.size(); i++) {
            for (int j = 0; j < dat.size(); j++) {
                if (sum.get(i).equals(dat.get(j).get(dat.get(0).size() - 1))) {
                    all++;
                }
            }
            if ((double) all / dat.size() == 0.0) {
                continue;
            }
            amount += ((double) all / dat.size()) * (Math.log(((double) all / dat.size())) / Math.log(2.0));
            all = 0;
        }
        if (amount == 0.0) {
            return 0.0;
        }
        return -amount;//计算信息熵
    }

    //计算条件熵并返回信息增益值
    public double condition(int a, ArrayList<ArrayList<String>> dat) {
        ArrayList<String> all = new ArrayList<String>();
        double c = 0.0;
        all.add(dat.get(0).get(a));
        //得到属性种类
        for (int i = 0; i < dat.size(); i++) {
            if (!all.contains(dat.get(i).get(a))) {
                all.add(dat.get(i).get(a));
            }
        }
        ArrayList<ArrayList<String>> plus = new ArrayList<ArrayList<String>>();
        //部分分组
        ArrayList<ArrayList<ArrayList<String>>> count = new ArrayList<ArrayList<ArrayList<String>>>();
        //分组总和
        for (int i = 0; i < all.size(); i++) {
            for (int j = 0; j < dat.size(); j++) {
                if (all.get(i).equals(dat.get(j).get(a))) {
                    plus.add(dat.get(j));
                }
            }
            count.add(plus);
            c += ((double) count.get(i).size() / dat.size()) * Ent(count.get(i));
            plus.removeAll(plus);
        }
        return (Ent(dat) - c);
        //返回条件熵
    }

    //计算信息增益最大属性
    public int gain(ArrayList<ArrayList<String>> dat) {
        ArrayList<Double> num = new ArrayList<Double>();
        //保存各信息增益值
        for (int i = 0; i < dat.get(0).size() - 1; i++) {
            num.add(condition(i, dat));
        }
        int index = 0;
        double max = num.get(0);
        for (int i = 1; i < num.size(); i++) {
            if (max < num.get(i)) {
                max = num.get(i);
                index = i;
            }
        }
        //System.out.println("<"+label.get(index)+">");
        return index;
    }

    //构建决策树
    public TreeNode createTree(ArrayList<ArrayList<String>> dat) {
        int index = gain(dat);
        TreeNode node = new TreeNode(label.get(index));
        ArrayList<String> s = new ArrayList<String>();//属性种类
        s.add(dat.get(0).get(index));
        //System.out.println(dat.get(0).get(index));
        for (int i = 1; i < dat.size(); i++) {
            if (s.contains(dat.get(i).get(index)) == false) {
                s.add(dat.get(i).get(index));
                //System.out.println(dat.get(i).get(index));
            }
        }
        ArrayList<ArrayList<String>> plus = new ArrayList<ArrayList<String>>();
        //部分分组
        ArrayList<ArrayList<ArrayList<String>>> count = new ArrayList<ArrayList<ArrayList<String>>>();
        //分组总和
        //得到节点下的边标签并分组
        for (int i = 0; i < s.size(); i++) {
            node.label.add(s.get(i));//添加边标签
            //System.out.print("添加边标签:"+s.get(i)+"  ");
            for (int j = 0; j < dat.size(); j++) {
                if (true == s.get(i).equals(dat.get(j).get(index))) {
                    plus.add(dat.get(j));
                }
            }
            count.add(plus);
            //System.out.println();
            //以下添加结点
            int k;
            String str = count.get(i).get(0).get(count.get(i).get(0).size() - 1);
            for (k = 1; k < count.get(i).size(); k++) {
                if (false == str.equals(count.get(i).get(k).get(count.get(i).get(k).size() - 1))) {
                    break;
                }
            }
            if (k == count.get(i).size()) {
                TreeNode dd = new TreeNode(str);
                node.nodes.add(dd);
                //System.out.println("这是末端:"+str);
            } else {
                //System.out.print("寻找新节点:");
                node.nodes.add(createTree(count.get(i)));
            }
            plus.removeAll(plus);
        }
        return node;
    }

    //输出决策树
    public void printTree(ArrayList<ArrayList<String>> dat) {
        System.out.println("构建的决策树如下：");
        TreeNode node = null;
        node = createTree(dat);//类
        put(node);//递归调用
    }

    //用于递归的函数
    public void put(TreeNode node) {
        System.out.println("结点：" + node.getName() + "\n");
        for (int i = 0; i < node.label.size(); i++) {
            System.out.println(node.getName() + "的标签属性:" + node.label.get(i));
            if (node.nodes.get(i).nodes.isEmpty() == true) {
                System.out.println("叶子结点：" + node.nodes.get(i).getName());
            } else {
                put(node.nodes.get(i));
            }
        }
    }

    //用于对待决策数据进行预测并将结果保存在指定路径
    public void forecastData(ArrayList<ArrayList<String>> test) throws IOException {
        TreeNode node = null;
        int count = 0;
        node = createTree(this.data);//类
        for (int i = 0; i < test.size(); i++) {
            testPut(node, test.get(i));//递归调用
            if (kind.equals(data.get(i).get(data.get(i).size() - 1)) == true) {
                count++;
            }
        }
//        System.out.println("该次分类结果正确率为："+(double)count/test.size()*100+"%");
    }

    //用于测试的递归调用
    public void testPut(TreeNode node, ArrayList<String> t) {
        int index = 0;
        for (int i = 0; i < this.label.size(); i++) {
            if (this.label.get(i).equals(node.getName()) == true) {
                index = i;
                break;
            }
        }
        for (int i = 0; i < node.label.size(); i++) {
            if (t.get(index).equals(node.label.get(i)) == false) {
                continue;
            }
            if (node.nodes.get(i).nodes.isEmpty() == true) {
                //System.out.println("分类结果为："+node.nodes.get(i).getsname());
                this.kind = node.nodes.get(i).getName();//取出分类结果
            } else {
                testPut(node.nodes.get(i), t);
            }
        }
    }
}
